前情提要:在上一篇網頁UI組件化 — React component,大致了解 React 最重要的核心 — Component,並且學會了在組件內控制資料的處理,接下來就來探討 React 其他更便利的功能。
在某些狀況下,你可能會希望依據 State 資料狀態,來改變 Render 的內容,React 就允許使用 JavaScript 的 if
或三元運算子,控制改變要渲染的 Element。 比如我們延續上一篇文章使用的計數器範例,要在分數小於零時控制 minus_btn
不顯示,不讓使用者減少分數。
在 Counter.js 宣告一個變數 minus_btn
儲存 Element,初始值為 null
,代表沒有指定 Element,顯示在畫面上就會是空,會是分數小於零不顯示 minus_btn
的情況。
Counter.js
export default function Counter() {
...
let minus_btn = null;
return (
<div className="Counter">
...
</div>
);
}
接著要利用 if
,在分數大於零的時候要指定 Element 給變數 minus_btn
,並將原本 render()
內的 Element 換成 minus_btn
控制。
export default function Counter() {
...
let minus_btn = null;
if (score > 0)
minus_btn = <button onClick={() => setScore(score - 1)}>minus</button>;
return (
<div className="Counter">
...
<button onClick={() => setScore(score + 1)}>Plus</button>
{minus_btn}
</div>
);
}
這樣就完成條件 Render 了
另外你還可以換成用三元運算子:條件 ? (條件為 true 時的值) : (條件為 false 時的值)
,直接在 render()
內改變 Element 的顯示。
export default function Counter() {
...
return (
<div className="Counter">
...
<button onClick={() => setScore(score + 1)}>Plus</button>
{score > 0 ? <button onClick={() => setScore(score - 1)}>minus</button> : null}
</div>
);
}
目前我們是直接複製 Member Component 產生三個成員,假設成員數目增加,這種複製的做法就顯得冗長又沒效率。所以可以使用 JavaScript 的陣列處理方法 map()
,該方法會合併陣列每一個元素回傳的運算結果,建立一個新的陣列。
遍歷資料 members
內部的每個成員 member
,生成 Member Component 並代入名字資料,再將所有回傳的結果組合新陣列指定給變數 element_list
,就可以直接依據資料自動 Render 多個 Component。
App.js
export default function App() {
const members = ["May", "Julia", "Bob"];
const element_list = members.map((member) => <Member name={member} />);
return (
<div className="App">
<h1>Member Score</h1>
{element_list}
</div>
);
}
但這時候你會看到 Console 視窗出現一個錯誤:Warning: Each child in list should have a unique "key" prop.
,告訴我們應該為每個 <Member />
新增一個不重複的 key
。
為什麼需要Key?Key 的功用在於可以透過幫助 React 來分辨哪些項目被改變、增加或刪除,進而進行畫面的更新,為了不讓 React 搞錯每個 Element 的身分,每個元素都要有一個獨立的 Key 值。所以我們就直接為 <Member />
新增 key
,指定每位成員的姓名為其值。
export default function App() {
const members = ["May", "Julia", "Bob"];
const element_list = members.map((member) => <Member key={member} name={member} />);
return (
<div className="App">
<h1>Member Score</h1>
{element_list}
</div>
);
}
之前我們有提到在 JSX 內可以放入任何表達式,前面的作法是另外宣告變數 element_list
存放列表元素,再用大括號包起來放到 JSX 中;另一個作法則是可以改成將 map()
放在 JSX 中:
return (
<div className="App">
...
{members.map((member) => (
<Member key={member} name={member} />
))}
</div>
);
在 React 中,資料傳遞的方式屬於單向資料流,代表所有的資料都只能透過 props
,從父層 Component 往子層 Component 傳遞。但當我們希望不同的 Component 間可以共享某些數據,比如接下來想要在 Member Component 裡,顯示成員成績的狀態是合格還是不及格,就需要共享 Counter Component 內的 Score State。
解決的方法就是提升 State 的層級,將共享的資料提升到組件匯流的最上層,舉例來說如下圖,B 需要共享 C 的 Data State,那我們就將 State 提升到父層組件 A,再透過 Props 傳遞 Data 給 B 和 C。
在我們的範例中,設定分數大於零就顯示 PASS、否則顯示 FAIL,Member Component 就需要共享 Counter Component 內的 Score State。所以將 State 提升到最上層的組件 Member Component,並將資料透過 Props 傳遞給 Counter Compenent。
Member.js
import { useState } from "react";
import Counter from "./Counter";
export default function Member(props) {
const [score, setScore] = useState(0);
return (
<div className="member">
<h2>{props.name}</h2>
<div>{score > 0 ? "PASS" : "FAIL"}</div>
<Counter score={score}/>
</div>
);
}
問題是 Props 是唯讀的,不能在子組件內修改父層 State 的值,那要如何修改計數器按鈕 onClick 所觸發的動作?我們可以透過 Props 傳遞動作,先在 Member Component 設定好按下按鈕後,修改 Score State 的動作handleScoreChange
,再將函式作為 Props 傳遞給 Counter Component 呼叫。
Member.js
export default function Member(props) {
...
function handleScoreChange(number) {
//接收分數加減完成後的number
setScore(number);
}
return (
<div className="member">
...
<Counter score={score} handleScoreChange={handleScoreChange}/>
</div>
);
}
Counter.js
export default function Counter(props) {
return (
<div className="Counter">
...
<button onClick={() => props.handleScoreChange(props.score + 1)}>
Plus
</button>
{props.score > 0 && (
<button onClick={() => props.handleScoreChange(props.score - 1)}>
minus
</button>
)}
</div>
);
}
分數大於零就顯示PASS、否則顯示FAIL
學會了 React 其他的功能,包括如何有條件的 Render Element、用 map()
自動生成列表和提升 State 層級,React 本身的介紹差不多就告一個段落了。當你從單純撰寫 HTML、CSS、JS,慢慢轉變為在專案中套入框架,用框架的模式去思考,就會體會到這種模組化的方式可以幫助我們,讓專案更好開發與管理。接下來就會進一步提到如何使用 Redux,能夠更好的管理應用程式中的資料狀態。
如果文章中有錯誤的地方,要麻煩各位大大不吝賜教;喜歡的話,也要記得幫我按讚訂閱喔❤️
參考資料